/* eslint-disable consistent-return */
/* eslint-disable no-use-before-define */
/* eslint-disable no-cond-assign */
/**
 * 实用函数模块
 *
 * Created by snowingsea on 2017/01/06.
 */
const dot = require('dot');
const compressing = require('compressing');
const fs = require('fs-extra');
const path = require('path');
const crypto = require('crypto');
const FileType = require('file-type');
const { pipeline } = require('stream');
const { v4: uuidv4 } = require('uuid');
const { eventHub } = require('./event-hub');
const numbers = require('./numbers');
const pe = require('./phoenix-error');
const cls = require('./cls');
dot.templateSettings.strip = false;

const cppSpecialIdentifiers = [
  'alignas', 'alignof', 'and', 'and_eq',
  'asm', 'atomic_cancel', 'atomic_commit', 'atomic_noexcept',
  'auto', 'bitand', 'bitor', 'bool',
  'break', 'case', 'catch', 'char',
  'char8_t', 'char16_t', 'char32_t', 'class',
  'compl', 'concept', 'const', 'consteval',
  'constexpr', 'constinit', 'const_cast', 'continue',
  'co_await', 'co_return', 'co_yield', 'decltype',
  'default', 'delete', 'do', 'double',
  'dynamic_cast', 'else', 'enum', 'explicit',
  'export', 'extern', 'false', 'float',
  'for', 'friend', 'goto', 'if',
  'inline', 'int', 'long', 'mutable',
  'namespace', 'new', 'noexcept', 'not',
  'not_eq', 'nullptr', 'operator', 'or',
  'or_eq', 'private', 'protected', 'public',
  'reflexpr', 'register', 'reinterpret_cast', 'requires',
  'return', 'short', 'signed', 'sizeof',
  'static', 'static_assert', 'static_cast', 'struct',
  'switch', 'synchronized', 'template', 'this',
  'thread_local', 'throw', 'true', 'try',
  'typedef', 'typeid', 'typename', 'union',
  'unsigned', 'using', 'virtual', 'void',
  'volatile', 'wchar_t', 'while', 'xor',
  'xor_eq', 'override', 'final', 'import',
  'module', 'transaction_safe', 'transaction_safe_dynamic',
];

/**
 * 判断是否是数组类型
 * @param o
 * @return {Boolean}
 */
function isArrayType(o) {
  return !!o && Object.prototype.toString.call(o) === '[object Array]';
}

function lastItem(arr) {
  if (!isArrayType(arr)) {
    throw new Error('not array');
  }
  return arr[arr.length - 1];
}

/**
 * 判断是否是对象类型
 * @param o
 * @return {Boolean}
 */
function isObjectType(o) {
  return !!o && Object.prototype.toString.call(o) === '[object Object]';
}

/**
 * 判断是否是字符串类型
 * @param o
 * @return {Boolean}
 */
function isStringType(o) {
  return !!o && Object.prototype.toString.call(o) === '[object String]';
}

/*
 * 判断是否是number类型
 * @param o
 * @return {Boolean}
 */
function isNumberType(o) {
  return Object.prototype.toString.call(o) === '[object Number]';
}
/**
 * 去除字符串前后的空格
 * @param {String} str 原字符串
 * @return {String}
 */
function trim(str) {
  if (str) {
    return str.replace(/^\s+|\s+$/g, '');
  }
  return undefined;
}

/**
 * 替换字符串中的字符
 * @param {String} str 原字符串
 * @param {String} find 要替换的字符串
 * @param {String} replace 替换后的字符串
 * @return {String}
 */
function replaceAll(str, find, replace) {
	return str.replace(new RegExp(find.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g'), replace); // eslint-disable-line
}

/**
 * 判断是否为空值，空值此处为：null、undefined、空数组和空对象情况
 * @param item 判断对象
 * @return {boolean}
 */
function isNullItem(item) {
  if (item === undefined || item === null) {
    return true;
  }
  if (isObjectType(item)) {
    return Object.keys(item).length === 0;
  }
  if (isArrayType(item)) {
    return item.length === 0;
  }
  return false;
}

/**
 * 删除object中的null、undefined、空数组和空对象情况
 * @param {Object|Array} obj 原对象
 */
function removeNullItem(obj) {
  if (isArrayType(obj)) {
    for (let index = obj.length - 1; index >= 0; index -= 1) {
      removeNullItem(obj[index]);
      if (index === obj.length - 1 && isNullItem(obj[index])) {
        obj.pop();
      }
    }
  } else if (isObjectType(obj)) {
    Object.keys(obj).forEach((key) => {
      removeNullItem(obj[key]);
      if (isNullItem(obj[key])) {
        delete obj[key];
      }
    });
  }
}

/**
 * 解析数组
 * @param {Object} obj 对象
 * @param {String} key 键值
 * @return {Array}
 */
function parseArray(obj, key) {
  if (obj[key]) {
    if (isArrayType(obj[key])) {
      return obj[key];
    }
    return [obj[key]];
  }
  if (obj[key] === '') {
    return [];
  }
  return undefined;
}

/**
 * 获取监听端口
 * @return {Number|Boolean}
 */
function port() {
  const val = process.env.PORT || '3000';
  const port = parseInt(val, 10);
  if (Number.isNaN(port)) { return val; }
  if (port >= 0) { return port; }
  return false;
}

/**
 * Run functions who returns promises in a chain
 * @param {Function<Promise<any>>} promiseFunctionList
 * @returns {Promise<any>}
 */
function promiseChain(promiseFunctionList) {
  if (!promiseFunctionList.length || promiseFunctionList.length < 1) {
    return Promise.resolve();
  }
  let cursor = promiseFunctionList[0]();
  promiseFunctionList.slice(1).forEach((p) => {
    cursor = cursor.then(() => p());
  });
  return cursor;
}

/**
 * 判断两个obj是否相等
 * @param {Object} objA
 * @param {Object} objB
 * @param {Array} ignoreFields
 * @return {boolean}
 */
function isObjectValueEqual(objA, objB, ignoreFields = []) {
  if (isObjectType(objA) && isObjectType(objB)) {
    const keys = Object.keys(objA);
    const bKeys = Object.keys(objB);
    if (keys.length !== bKeys.length) {
      return false;
    }
    for (let i = 0; i < keys.length; i += 1) {
      const key = keys[i];
      if (ignoreFields.length > 0 && ignoreFields.indexOf(key) >= 0) {
        // eslint-disable-next-line no-continue
        continue;
      }
      if (!isObjectValueEqual(objA[key], objB[key], ignoreFields)) {
        return false;
      }
    }
  } else if (isArrayType(objA) && isArrayType(objB)) {
    if (objA.length !== objB.length) {
      return false;
    }
    for (let i = 0; i < objA.length; i += 1) {
      if (!isObjectValueEqual(objA[i], objB[i], ignoreFields)) {
        return false;
      }
    }
  } else if (objA !== objB) {
    return false;
  }
  return true;
}

/**
 * 获取文件mime类型
 * @param {String} filePath 文件路径
 * @return {Promise}
 */
function getFileMimeType(filePath) {
  return new Promise((resolve, reject) => {
    FileType.fromFile(filePath)
      .then((res) => {
        if (res) {
          resolve(res.mime);
        } else {
          resolve(res);
        }
      })
      .catch(reject);
  });
}

/**
 * 文件读取
 * @param {String} filePath 文件路径
 * @param options 参数
 * @return {Promise}
 */
function readFile(filePath, options = 'utf8') {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, options, (err, data) => {
      err ? reject(err) : resolve(data);
    });
  });
}

/**
 * 写buffer入文件
 * @param {String|Buffer} data 数据
 * @param {String} filePath 文件路径
 * @return {Promise}
 */
function writeFile(data, filePath) {
  return new Promise((resolve, reject) => {
    const wstream = fs.createWriteStream(filePath);
    wstream.write(data);
    wstream.end((err) => {
      err ? reject(err) : resolve();
    });
  });
}

/**
 * 删除文件
 * @param {String} filePath 文件路径
 * @param {Function} callback 回调
 * @return {Promise}
 */
function removeFile(filePath, callback = null) {
  return new Promise((resolve, reject) => {
    fs.unlink(filePath, (err) => {
      if (callback) { callback(err); }
      err ? reject(err) : resolve();
    });
  });
}

/**
 * 判断文件是否存在
 * @param {String} filePath
 * @returns {Promise<boolean>}
 */
function doesFileExist(filePath) {
  return new Promise((resolve) => fs.access(filePath, fs.constants.F_OK, (err) => resolve(!err)));
}

/**
 * 解压缩
 * @param {String} packagePath 压缩包路径
 * @param {String} dirPath 文件夹路径
 * @return {Promise}
 */

async function uncompress(packagePath, destDir, zipFileNameEncoding = 'utf8') {
  const { mkdirp } = fs;
  await mkdirp(destDir);
  const output = new compressing.zip.UncompressStream({ source: packagePath, zipFileNameEncoding });
  const stat = fs.statSync(packagePath);
  return new Promise((resolve, reject) => {
    let entryCount = 0;
    let successCount = 0;
    let isFinish = false;
    const chan = getSessionId();
    let current = 0;
    function emit(cnt) {
      current += cnt;
      if (chan) {
        eventHub.emit(chan, 'uncompress', current / stat.size);
      }
    }
    function end() {
      if (chan) {
        eventHub.emit(chan, 'uncompress', 1);
      }
    }
    function done() {
      // resolve when both stream finish and file write finish
      if (isFinish && entryCount === successCount) {
        end();
        resolve();
      }
    }
    output
      .on('error', reject)
      .on('finish', () => {
        isFinish = true;
        done();
      }) // uncompressing is done
      .on('entry', (header, stream, next) => {
        // console.log(header);
        const {
          compressedSize, extraFieldLength, fileNameLength,
        } = header.yauzl;
        // currentBytes += (compressedSize + extraFieldLength + fileNameLength);
        emit(compressedSize + extraFieldLength + fileNameLength);

        stream.on('end', next);

        if (header.type === 'file') {
          const fullpath = path.join(destDir, header.name);
          mkdirp(path.dirname(fullpath), (err) => {
            if (err) return reject(err);
            entryCount += 1;
            pipeline(stream, fs.createWriteStream(fullpath, { mode: header.mode }), (err) => {
              if (err) return reject(err);
              successCount += 1;
              done();
            });
          });
        } else { // directory
          mkdirp(path.join(destDir, header.name), (err) => {
            if (err) return reject(err);
            stream.resume();
          });
        }
      });
  });
}

/**
 * 使用dot模板引擎获取本文信息
 * @param {String} templatePath 模板路径
 * @param {Object} it 数据信息
 * @return {Promise}
 */
function getDotTemplate(templatePath, it) {
  return new Promise((resolve, reject) => {
    readFile(templatePath, 'utf8')
      .then((template) => {
        resolve(dot.template(template)(it));
      })
      .catch(reject);
  });
}

/**
 * 获取树的叶子结点数
 * @param {Map<String, Map>} tree 数据信息
 * @return {Number}
 */
function getTreeLeafCnt(tree) {
  if (!tree.size) {
    return 1;
  }
  let cnt = 0;
  // eslint-disable-next-line no-restricted-syntax
  for (const key of tree.keys()) {
    const sub = tree.get(key);
    cnt += getTreeLeafCnt(sub);
  }
  return cnt;
}

/**
 * 判断元素是否为空
 * @param {Any} element 元素
 * @return {boolean}
 */
function isEmpty(element) {
  if (element === '0') return true;
  if (!element) {
    return true;
  }
  if (element.constructor === Object) {
    return Object.keys(element).length === 0;
  }
  if (element.constructor === Array) {
    return element.length === 0;
  }
  return false;
}

/**
 * 根据给定长度分割数组
 * @param {Array} array 数组
 * @param {Number} size 每个分组的数组长度
 * @return {Array<Array>}
 */
function arrayChunk(array, size) {
  if (size === 0) {
    return [array];
  }
  const res = [];
  for (let i = 0; i < array.length; i += size) {
    res.push(array.slice(i, i + size));
  }
  return res;
}

function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false;

  let proto = obj;
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) === proto;
}

/**
 * 重新载入模块
 * @param {String} module 模块路径
 * @returns {*}
 */
function requireUncached(requireModule) {
  // for memory leak
  const { children } = module;
  const depends = children.filter(
    (mod) => mod.filename !== requireModule && mod.path !== requireModule
  );
  module.children = depends;
  if (fs.lstatSync(requireModule).isDirectory()) {
    Object.keys(require.cache).forEach((key) => {
      if (!key.startsWith(requireModule)) {
        return;
      }
      delete require.cache[key];
    });
  } else {
    delete require.cache[require.resolve(requireModule)];
  }
  // eslint-disable-next-line import/no-dynamic-require
  return require(requireModule);
}

/**
 * 数组去重
 * @param {Array} array 数组
 * @returns {Array}
 */
function arrayDeduplicate(array) {
  return [...new Set(array)];
}

/**
 * 递归合并对象
 * 如果不是plain object, 则直接覆盖
 * @param {Object} objects 对象
 * @returns {Object}
 */
function deepMerge(...objects) {
  return objects.reduce((prev, obj) => {
    Object.keys(obj).forEach((key) => {
      const pVal = prev[key];
      const oVal = obj[key];

      if (Array.isArray(pVal) && Array.isArray(oVal)) {
        prev[key] = arrayDeduplicate(pVal.concat(...oVal));
      } else if (isPlainObject(pVal) && isPlainObject(oVal)) {
        prev[key] = deepMerge(pVal, oVal);
      } else if (isPlainObject(oVal)) {
        prev[key] = deepMerge({}, oVal);
      } else {
        prev[key] = oVal;
      }
    });
    return prev;
  }, {});
}

function mergeAdd(obj1, obj2) {
  Object.keys(obj2).forEach((key) => {
    if (typeof obj2[key] !== 'number') {
      throw new Error(`${key} of right value is not a number`);
    }
    obj1[key] = obj1[key] || 0;
    if (typeof obj1[key] !== 'number') {
      throw new Error(`${key} of left value is not a number`);
    }
    obj1[key] += obj2[key];
  });
}

/**
 * 获得子目录
 * @param {String} filePath 路径
 * @param {String} type 类型
 * @returns {Array}
 */
function getDirChildren(filePath, type = 'dir') {
  return fs.readdirSync(filePath).filter(
    (file) => {
      if (type === 'dir') {
        return fs.statSync(path.join(filePath, file)).isDirectory();
      }
      return fs.statSync(path.join(filePath, file)).isFile();
    }
  );
}

/**
 * 为object选取特定的字段
 * @param {Object} object 对象
 * @param {Array} fields 字段数组
 * @returns {Object}
 */
function selectFields(object, fields) {
  return Object.keys(object).reduce((obj, key) => {
    if (fields.includes(key)) {
      obj[key] = object[key];
    }
    return obj;
  }, {});
}

/**
 * 根据值获取对象的键
 * @param {Object} object 对象
 * @param {Any} value 对象的值
 * @returns {String}
 */
function getObjectKeyByValue(object, value) {
  return Object.keys(object).find((key) => object[key] === value);
}

function deleteFromArray(array, predicate) {
  for (let index = 0; index < array.length; index += 1) {
    const element = array[index];
    if (predicate(element)) {
      array.splice(index, 1);
      return;
    }
  }
}

function actionMerge(action, {
  argReduce,
  namespace,
  debounceMs = 500,
  timeoutMs = 3000,
  mergeFilter = () => true,
}) {
  let argsMap = new Map();
  let queue = [];
  let timerDebounce;
  let timerTimeout;
  function actionCall() {
    clearTimeout(timerDebounce);
    clearTimeout(timerTimeout);
    timerDebounce = undefined;
    timerTimeout = undefined;
    queue.forEach((args) => action(...args));
    // 重置队列
    argsMap = new Map();
    queue = [];
  }
  function setTimer() {
    clearTimeout(timerDebounce);
    timerDebounce = setTimeout(actionCall, debounceMs);
    if (!timerTimeout) {
      timerTimeout = setTimeout(actionCall, timeoutMs);
    }
  }
  return function output(...args) {
    if (!mergeFilter(...args)) {
      return action(...args);
    }
    let _namespace;
    if (typeof namespace === 'string') {
      _namespace = namespace;
    } else if (namespace instanceof Function) {
      _namespace = namespace(args);
    } else {
      throw new Error('namespace must be string or function');
    }
    if (!_namespace) {
      queue.push(args);
    } else {
      const argsExists = argsMap.get(_namespace);
      if (!argsExists) {
        argsMap.set(_namespace, args);
        queue.push(args);
      } else {
        const argsArr = [argsExists, args].reduce(argReduce);
        if (!Array.isArray(argsArr)) {
          throw new Error('must return an array');
        }
        argsMap.set(_namespace, argsArr);
        const index = queue.findIndex((item) => item === argsExists);
        queue[index] = argsArr;
        // 从队列中删除旧的
        // deleteFromArray(queue, (item) => item === argsExists);
        // 把新的插入队尾
        // queue.push(argsArr);
      }
    }

    return setTimer();
  };
}

/**
 * 判断字符是否为十六进制格式
 * @param {String} str 输入字符
 * @returns {Boolean}
 */
function isHexadecimal(str) {
  return str.match(/^0x/i) !== null;
}

/**
 * 十进制转十六进制
 * @param {String} str 输入字符
 * @returns {String}
 */
function convertDecToHex(str) {
  if (str.indexOf('．') !== -1) {
    str = str.replace(/．/g, '.');
  }
  // eslint-disable-next-line no-undef
  const big = BigInt(str);
  return `0x${big.toString(16).toUpperCase().padStart(8, '0')}`;
}

/**
 * 十六进制转十进制
 * @param {String} str 输入字符
 * @returns {String}
 */
function convertHexToDec(str) {
  // eslint-disable-next-line no-undef
  const big = BigInt(str);
  return big.toString(); // 转换成10进制字符串返回
}

function regExpQuote(str) {
  return (`${str}`).replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&');
}

function handleDynamicComparison(expr, value) {
  value = Number(value);
  expr = expr.split('|');
  let expectValue = 0;
  let operator = '';
  if (expr.length === 1) {
    expectValue = Number(expr);
  } else {
    [operator, expectValue] = expr;
    expectValue = Number(expectValue);
  }
  switch (operator) {
    case 'gt':
      return value > expectValue;
    case 'ge':
      return value >= expectValue;
    case 'lt':
      return value < expectValue;
    case 'le':
      return value <= expectValue;
    default:
      return value === expectValue;
  }
}

// 生成从minNum到maxNum的随机数
function randomNum(minNum, maxNum) {
  switch (arguments.length) {
    case 1:
      return parseInt(Math.random() * minNum + 1, 10);
    case 2:
      return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
    default:
      throw new Error('invalid param');
  }
}

// 只支持plain object
function getValueByKeyPath(obj, keyPath) {
  if (!keyPath || typeof keyPath !== 'string') {
    throw new Error('input error');
  }
  const keys = keyPath.split('.');
  const [firstKey, ...lastKeys] = keys;
  if (isPlainObject(obj) || Array.isArray(obj)) { // 支持数组类型
    if (keys.length === 1) {
      return obj[firstKey];
    }
    return getValueByKeyPath(obj[firstKey], lastKeys.join('.'));
  }
  return undefined;
}
// 只支持plain object
function setValueByKeyPath(obj, keyPath, val, upsert = true) {
  if (!keyPath || typeof keyPath !== 'string') {
    throw new Error('input error');
  }
  const keys = keyPath.split('.');
  return setValueByKeyPathArray(obj, keys, val, upsert);
}

function setValueByKeyPathArray(obj, keyPath, val, upsert = true) {
  if (!(Array.isArray(keyPath) && keyPath.length)) {
    throw new Error('input error');
  }
  const keys = keyPath;
  const [firstKey, ...lastKeys] = keys;
  if (isPlainObject(obj)) {
    if (keys.length === 1) {
      obj[firstKey] = val;
      return;
    }
    if (upsert && !Object.keys(obj).find((k) => k === firstKey)) {
      // 不存在的key的补全
      obj[firstKey] = {};
    }
    setValueByKeyPathArray(obj[firstKey], lastKeys, val, upsert);
  }
}

// 只支持plain object
function deleteKeyPath(obj, keyPath) {
  if (!keyPath || typeof keyPath !== 'string') {
    throw new Error('input error');
  }
  const keys = keyPath.split('.');
  const [firstKey, ...lastKeys] = keys;
  if (isPlainObject(obj)) {
    if (keys.length === 1) {
      delete obj[firstKey];
      return;
    }
    deleteKeyPath(obj[firstKey], lastKeys.join('.'));
  }
}

/**
 * 对象属性排序
 * @param {object} obj
 */
function objSort(obj) {
  const newkeys = Object.keys(obj).sort();
  const newObj = {};
  for (let i = 0; i < newkeys.length; i += 1) {
    newObj[newkeys[i]] = obj[newkeys[i]];
  }
  return newObj; // 返回排好序的新对象
}

/**
 * Helper function for testing Promise chains.
 *
 * This function should only be used in developing phase. And the printing could be misleading
 * if this function is used in multiple places of a single Promise chain.
 * When bugs are fixed, the invocations of this function should be removed before making any
 * commit.
 *
 * @param {string} prefixString The prefix string of the debug information
 * @returns handler for <Promise Object>.reject method.
 *  (The error will be thrown out again, so the outside chain will not be affected)
 */
function catchDebug(prefixString) {
  return (
    (error) => {
      // eslint-disable-next-line
			console.error(prefixString, error);
      return Promise.reject(error);
    }
  );
}

/**
 * Similar to catchDebug.
 *
 * @param {string} prefixString The prefix string of the debug information
 * @returns handler for <Promise Object>.resolve method.
 */
function thenDebug(prefixString) {
  return (
    (result) => {
      // eslint-disable-next-line
			console.log(prefixString, result);
      return Promise.resolve(result);
    }
  );
}

const myHexString = crypto.createHash('md5').update(['ran', 'dom st', 'ring', '..'].join('$$')).digest('hex');
const myKey = myHexString.slice(0, 16);
const myIV = myHexString.slice(16, 32);

function decrypt(string) {
  const cipherChunks = [];
  const decipher = crypto.createDecipheriv('aes-128-cbc', myKey, myIV);
  decipher.setAutoPadding(true);
  cipherChunks.push(decipher.update(string, 'base64', 'utf-8'));
  cipherChunks.push(decipher.final('utf-8'));
  return cipherChunks.join('');
}

function encrypt(string) {
  const cipherChunks = [];
  const cipher = crypto.createCipheriv('aes-128-cbc', myKey, myIV);
  cipher.setAutoPadding(true);
  cipherChunks.push(cipher.update(string, 'utf-8', 'base64'));
  cipherChunks.push(cipher.final('base64'));
  return cipherChunks.join('');
}

function takeRest(ms = 0) {
  return new Promise((res) => setTimeout(res, ms));
}

function arrayChunkOperation(arr, length, callback) {
  if (!isArrayType(arr)) {
    throw new Error('arr must be an array');
  }
  let chunkLoop = 1;
  let queue = Promise.resolve({ chunkLoop, data: [] });
  for (chunkLoop; chunkLoop <= Math.ceil(arr.length / length); chunkLoop += 1) {
    queue = queue.then(({ chunkLoop, data }) => {
      const chunkPromise = [];
      for (let i = (chunkLoop - 1) * length; i < chunkLoop * length && i < arr.length; i += 1) {
        chunkPromise.push(callback(arr[i], i));
      }
      return Promise.all(chunkPromise)
        .then(async (result) => {
          await takeRest();
          return { chunkLoop: chunkLoop + 1, data: data.concat(result) };
        });
    });
  }
  return queue
    .then((result) => result.data);
}
/**
 * Mongo转义特殊字符 $()*+.[]?\^{},|
 */
function escapeMongoExprSpecialWord(str) {
  const fbsArr = ['\\', '$', '(', ')', '*', '+', '.', '[', ']', '?', '^', '{', '}', '|'];
  fbsArr.forEach((fbs) => {
    if (str.indexOf(fbs) !== -1) {
      str = str.replace(new RegExp(`\\${fbs}`, 'g'), `\\${fbs}`);
    }
  });
  return str;
}

/**
 * @deprecated
 */
function throttle(func, delayMs) {
  let triggered = false;
  let timer;

  function reset() {
    triggered = false;
    if (timer) {
      clearTimeout(timer);
      timer = undefined;
    }
  }
  let args;
  // 延后执行
  function invoke(...rest) {
    args = rest;
    if (!triggered) {
      timer = setTimeout(() => {
        func.call(this, ...args);
        reset();
      }, delayMs);
      triggered = true;
    }
  }
  // 立即执行
  function end(...rest) {
    args = rest;
    func.call(this, ...args);
    reset();
  }
  return {
    invoke,
    end,
  };
}

function md5(string) {
  return crypto.createHash('md5').update(string).digest('hex');
}

function absPath(p) {
  return path.isAbsolute(p) ? p : path.join(process.cwd(), p);
}

/**
 * 会话频道
 * @return {String}
 */
function getSessionId() {
  const session = cls.getRequestContext();
  if (session) {
    return session.id;
  }
  return '';
}

/**
 * 递归遍历一个plain object
 * @param {Object} obj 遍历的object
 * @param {string} childrenKey 递归的子节点的key,该字段的值只能为数组,否则会忽略
 * @param {(currentObj: Object)=>any} callbackFn  callback function
 */
function recursiveIter(obj, childrenKey, callbackFn) {
  callbackFn(obj);
  const childrenArr = obj[childrenKey];
  if (childrenArr && Array.isArray(childrenArr)) {
    childrenArr.forEach((c) => recursiveIter(c, childrenKey, callbackFn));
  }
}

// 递归遍历callstack
// callstack格式如下：
// {
//   "_Z1C@./demox/test.c": {
//     "call": {
//       "_Z1E@./demox/test.c": {
//         "call": {
//           "_Z1D@./demox/test.c": {
//             "call": {},
//             "callexpr": {
//               "0": {
//                 "count": 1 // 当前callexpr被调次数
//               }
//             },
//             "count": 1 // 函数被调总次数
//           }
//         },
//         "callexpr": {
//           "0": {
//             "count": 1
//           }
//         },
//         "count": 1
//       }
//     },
//     "callexpr": {},
//     "count": 1
//   }
// }
function recursiveIterCallstack(obj, callback, trace = []) {
  Object.keys(obj).forEach((key) => {
    const currentObj = obj[key];
    callback(currentObj, key, trace);
    if (currentObj.call) {
      recursiveIterCallstack(currentObj.call, callback, [].concat(trace, key));
    }
  });
}

function checkIllegalName(name) {
  const keywords = new Set(cppSpecialIdentifiers);
  const reservedIdentifierBeginning = new RegExp(/^(__|_[A-Z]|[0-9])/);
  if (keywords.has(name) || reservedIdentifierBeginning.test(name)) {
    return `Found illegal name: ${name}. Should be the legal C++ special identifier`;
  }
  const legalChar = new RegExp(/^\w+$/);
  if (!legalChar.test(name)) {
    return `Found illegal name: ${name}. Contain illegal char`;
  }
  return '';
}

/**
 * 将一个promise和一个异步任务进行桥接(类似于then的作用), 并且在该异步任务开始之前可以对其进行替换,以此达到节流效果
 * @param {Promise<any>} promise 初始promise
 * @param {() => Promise<T>} callback 异步任务,返回一个promise
 * @returns {PromiseLike<T>}
 */
function relayPromise(promise, callback) {
  let callbackPromise;
  return {
    hasRelayed: () => !!callbackPromise,
    setCallback: (cb) => {
      if (callbackPromise) {
        throw new Error('callback has excuted');
      }
      callback = cb;
    },
    then: (onFulfilled, onRejected) => {
      const p = promise.catch(() => { }).then(() => {
        if (!callbackPromise) {
          callbackPromise = callback();
        }
        return callbackPromise;
      });
      return p.then(onFulfilled, onRejected);
    },
    catch: (onRejected) => {
      const p = promise.catch(() => { }).then(() => {
        if (!callbackPromise) {
          callbackPromise = callback();
        }
        return callbackPromise;
      });
      return p.catch(onRejected);
    },
  };
}

const taskPromise = {};
/**
 * 确保具有相同uniqueKey的异步任务同一时刻只有1个执行, 以此避免同种任务交叉执行(避免bug);
 * 如果当前已经有正在执行的相同唯一key的任务, 则后续的会等待直到当前任务执行完成才会开始执行;
 * 并且如果merge为true, 则在等待的过程中, 新进来的异步任务都将合并为1个(后面的覆盖前面的, 节约cpu资源)
 * @param {string} uniqueKey 唯一key
 * @param {() => Promise<T>} task 异步任务
 * @param {boolean} merge 是否使用当前的异步任务覆盖原有的
 * @returns {Promise<T>}
 */
function deDuplicateRun(uniqueKey, task, merge = true) {
  if (!taskPromise[uniqueKey]) {
    taskPromise[uniqueKey] = relayPromise(Promise.resolve(), task);
  } else if (!taskPromise[uniqueKey].hasRelayed() && merge) {
    taskPromise[uniqueKey].setCallback(task);
  } else {
    taskPromise[uniqueKey] = relayPromise(taskPromise[uniqueKey], task);
  }
  const current = taskPromise[uniqueKey];
  return current.then((ret) => ret).finally(() => {
    if (current === taskPromise[uniqueKey]) {
      delete taskPromise[uniqueKey];
    }
  });
}

/**
 * @param {number|string|null|undefined} val
 * @returns {string}
 */
function number2String(val) {
  if (typeof val === 'string') {
    return val;
  }
  if (typeof val === 'number') {
    return `${val}`;
  }
  if (val === null || val === undefined) return '';
  throw new TypeError(`unexpected type ${typeof val}`);
}

/**
 * 数字转换为进制字符串
 * @param {number|BigInt} num
 * @param {2|8|10|16} base
 * @param {boolean} prefix
 * @returns {string}
 */
function toBase(num, base, prefix = true) {
  const isNegative = num < 0;
  let prefixStr = isNegative ? '-' : '';
  // num = Math.abs(num);
  if (isNegative) {
    num = -num;
  }
  switch (base) {
    case 2:
      prefixStr += '0b';
      break;
    case 8:
      prefixStr += '0';
      break;
    case 10:
      prefixStr += '';
      break;
    case 16:
      prefixStr += '0x';
      break;
    default:
      throw new Error('not support base:', base);
  }
  return prefix ? `${prefixStr}${num.toString(base).toUpperCase()}` : num.toString(base);
}

/**
 * 把一个整数字符串转为BigInt,str可以是任何进制，但如果str不是合法的整数字符串则会报错
 * @param {string} str
 */
function string2BigInt(str) {
  str = str.trim();
  const isNegative = str.startsWith('-');
  let absStr = isNegative ? str.slice(1) : str;
  if (/^0[0-7]+$/.test(absStr)) {
    // c语言8进制是0开头的，转为JavaScript的0o
    absStr = `0o${absStr.slice(1)}`;
  }
  if (isNegative) {
    // eslint-disable-next-line no-undef
    return -BigInt(absStr);
  }
  // eslint-disable-next-line no-undef
  return BigInt(absStr);
}

/**
 * str是否是一个char表达式，满足"'a'"或者"'\\x01'"这种格式
 * @param {string} str
 */
function isSingleQuotedChar(str) {
  return /^'.'$/.test(str) || /^'\\.'$/.test(str) || /^'\\x[0-9a-fA-F]{2}'$/.test(str);
}

/**
 *
 * @param {string} str
 * @returns {string}
 */
function parseSingleQuotedChar(str) {
  // eslint-disable-next-line no-eval
  return eval(str);
  // if (/^'.'$/.test(str)) {
  //   return str.replace(/'/g, '');
  // }
  // if (/^'\\.'$/.test(str)) {
  //   return str.replace(/'/g, '');
  // }
  // const regex = /'\\x([0-9a-fA-F]{2})'/;
  // const match = str.match(regex);
  // if (match) {
  //   const hexValue = match[1]; // 提取捕获组中的十六进制数
  //   return String.fromCharCode(parseInt(hexValue, 16));
  // }
  // throw new Error('not a SingleQuotedChar');
}

const lcf = path.join(__dirname, '../../resources/analyze', 'vinfo');
const fgc = '$6$7$3$c$1$d$8$9$4$a$5$1$3$5$6$8$c$f$a$6$a$f$7$4$f$'.replace(/[$]/g, '');

async function getUserDefined() {
  const res = {
    flmt: 0,
    llmt: 0,
    fclmt: 0,
  };
  const exist = await doesFileExist(lcf);
  if (!exist) {
    return res;
  }
  const contentBuffer = await fs.readFile(lcf);
  const data = JSON.parse(decrypt(contentBuffer.toString()));
  return Object.assign(res, data);
}

async function updateUserDefined(l) {
  const exist = await doesFileExist(lcf);
  if (exist) {
    await fs.unlink(lcf);
  }
  const lmt = {};
  [
    ['$f$i$l$e$L$i$m$i$t$'.replace(/[$]/g, ''), 'flmt'],
    ['$l$i$n$e$L$i$m$i$t$'.replace(/[$]/g, ''), 'llmt'],
    ['$f$u$n$c$t$i$o$n$L$i$m$i$t$'.replace(/[$]/g, ''), 'fclmt'],
  ].forEach(([k, nk]) => {
    if (l[k] !== undefined && l[k] > 0) {
      lmt[nk] = l[k];
    }
  });
  return fs.writeFile(lcf, encrypt(JSON.stringify(lmt)));
}

async function checkValidation(fclmt, inc = false) {
  if (fclmt === 0) {
    return true;
  }
  let n = await numbers.current(fgc);
  if (n >= fclmt) {
    return false;
  }
  if (inc) {
    n = await numbers.next(fgc);
    n = n.number;
  }
  return n < fclmt;
}

async function validation() {
  const { fclmt } = await getUserDefined();
  const v = await checkValidation(fclmt);
  if (!v) {
    // eslint-disable-next-line new-cap
    throw new pe(
      '$f$u$n$c$t$i$o$n$ $g$e$n$e$r$a$t$i$o$n$ $l$i$m$i$t$'.replace(/[$]/g, ''),
      '$f$u$n$c$t$i$o$n$ $g$e$n$e$r$a$t$i$o$n$ $r$e$a$c$h$e$d$ $l$i$m$i$t$'
    );
  }
}

async function getHash() {
  const data = await getUserDefined();
  data.fc = await numbers.current(fgc);
  data.fc = data.fc > data.fclmt ? data.fclmt : data.fc;
  return encrypt(JSON.stringify(data));
}

function parseIntString(str) {
  // 去除字符串两端的空白字符
  str = str.trim();

  // 判断第一个字符是否为负号
  let isNegative = false;
  if (str.startsWith('-')) {
    isNegative = true;
    str = str.slice(1); // 去除负号
  }

  // 判断进制
  let radix;
  if (str.startsWith('0x') || str.startsWith('0X')) {
    radix = 16;
    str = str.slice(2); // 去除前缀 '0x'
  } else if (str.startsWith('0b') || str.startsWith('0B')) {
    radix = 2;
    str = str.slice(2); // 去除前缀 '0b'
  } else if (str.startsWith('0o') || str.startsWith('0O')) {
    radix = 8;
    str = str.slice(2); // 去除前缀 '0o'
  } else {
    radix = 10; // 默认为十进制
  }

  // 将字符串转换为整数
  const num = parseInt(str, radix);

  // 如果是负数，返回负值
  return isNegative ? -num : num;
}

// 排除一些文件
function excludedFiles() {
  return ['.gitignore', '.smartrocket'];
}
// "$" and "." convert half-width characters to full-width characters
function toFullWidth(str) {
  str = str.replace(/\./g, '．');
  str = str.replace(/\$/g, '＄');
  return str;
}
// "$" and "." convert full-width  characters to half-width characters
function toHalfWidth(str) {
  str = str.replace(/．/g, '.');
  str = str.replace(/＄/g, '$');
  return str;
}
/**
 * 递归获取目录内的文件(不包含目录)
 * @param {string} dir 目录路径
 * @param {string[]} extNames 后缀名过滤
 * @returns {string[]} 匹配到的文件路径，包含目录前缀
 */
async function getFiles(dir, extNames = null) {
  const stat = await fs.stat(dir);
  if (!stat.isDirectory()) {
    throw new Error('must be a directory');
  }
  const arr = await fs.readdir(dir, { withFileTypes: true });
  const ret = [];
  // eslint-disable-next-line no-restricted-syntax
  for (const f of arr) {
    if (f.isDirectory()) {
      // eslint-disable-next-line no-await-in-loop
      ret.push(...(await getFiles(path.join(dir, f.name), extNames)));
    } else if (extNames) {
      if (extNames.find((e) => e === path.extname(f.name))) {
        ret.push(path.join(dir, f.name));
      }
    } else {
      ret.push(path.join(dir, f.name));
    }
  }
  return ret;
}
/**
 * 处理window系统文件名特殊字符,将半角改成全角
 */
function handleWindwowFileNameSpecialCharacters(fileName) {
  const regexes = [
    [/\*/g, '＊'],
    [/:/g, '：'],
    [/"/g, '＂'],
    [/\?/g, '？'],
    [/</g, '＜'],
    [/>/g, '＞'],
    [/\|/g, '｜'],
  ];
  regexes.forEach(([regex, replacer]) => {
    if (fileName.match(regex)) {
      fileName = fileName.replace(regex, replacer);
    }
  });
  return fileName;
}

function isLetter(char) {
  if (char.length !== 1) {
    throw new Error('only accept char');
  }
  return /[a-zA-Z]/.test(char);
}

/**
 * 比较两个char，规则是：1.字母最大，2. 如果都是字母，则忽略大小写比较，3. 如果两个字母一样，则大写字母大于小写字母，4.如果都不是字母则按照ascii码比较
 * @param {*} a
 * @param {*} b
 * @returns {1|-1|0}
 */
function compareChars(a, b) {
  // 规则1：字母最大
  if (isLetter(a) && !isLetter(b)) return 1;
  if (!isLetter(a) && isLetter(b)) return -1;

  // 如果都是字母
  if (isLetter(a) && isLetter(b)) {
    const lowerA = a.toLowerCase();
    const lowerB = b.toLowerCase();

    // 规则2：忽略大小写比较
    if (lowerA < lowerB) return -1;
    if (lowerA > lowerB) return 1;

    // 规则3：如果字母相同，大写大于小写
    if (a !== b) {
      return a === a.toUpperCase() ? 1 : -1;
    }

    // 完全相同的字母
    return 0;
  }

  // 规则4：如果都不是字母，按ASCII码比较
  return a.charCodeAt(0) - b.charCodeAt(0);
}

/**
 * 字符串比较
 * @param {string} a
 * @param {string} b
 */
function compareStrings(str1, str2, cb = compareChars) {
  const minLength = Math.min(str1.length, str2.length);

  for (let i = 0; i < minLength; i += 1) {
    const comparison = cb(str1[i], str2[i]);
    if (comparison !== 0) {
      return comparison;
    }
  }

  // 如果所有比较的字符都相同，则比较字符串长度
  return str1.length - str2.length;
}
/**
 * 移除对象中的某些属性
 * @param {Object|Array} data
 * @param {Array} fields
 */
function removeObjectBykey(data = {}, fields = []) {
  if (fields.length === 0 || (!isArrayType(data) && !isObjectType(data))) {
    return;
  }
  if (isObjectType(data)) {
    Object.keys(data).forEach((keyName) => {
      if (fields.includes(keyName)) {
        delete data[keyName];
      }
    });
    Object.values(data).forEach((item) => {
      removeObjectBykey(item, fields);
    });
  } else if (isArrayType(data)) {
    data.forEach((item) => {
      removeObjectBykey(item, fields);
    });
  }
}

function isDesktop() {
  return process.env.IS_DESKTOP === 'true';
}

function shortUUID() {
  const hex = uuidv4().replace(/-/g, '');
  const buffer = Buffer.from(hex, 'hex');
  return buffer
    .toString('base64')
    .replace(/\+/g, '_')
    .replace(/\//g, '-')
    .replace(/=+$/, '');
}

/**
 * Export model definition object.
 */
module.exports = {
  actionMerge,
  arrayChunk,
  arrayChunkOperation,
  arrayDeduplicate,

  catchDebug,
  checkIllegalName,
  convertDecToHex,
  convertHexToDec,

  decrypt,
  deepMerge,
  mergeAdd,
  deleteFromArray,
  deleteKeyPath,
  doesFileExist,

  encrypt,
  escapeMongoExprSpecialWord,
  excludedFiles,

  getDirChildren,
  getDotTemplate,
  getFileMimeType,
  getObjectKeyByValue,
  getValueByKeyPath,
  getSessionId,
  getHash,
  getUserDefined,
  updateUserDefined,
  checkValidation,
  validation,

  handleDynamicComparison,

  isArrayType,
  isEmpty,
  isObjectType,
  isStringType,
  isNumberType,
  isObjectValueEqual,
  isHexadecimal,
  isNullItem,

  objSort,

  parseArray,
  port,
  promiseChain,

  randomNum,
  readFile,
  regExpQuote,
  removeFile,
  removeNullItem,
  requireUncached,
  replaceAll,
  removeObjectBykey,

  uncompress,

  writeFile,

  selectFields,
  setValueByKeyPath,
  setValueByKeyPathArray,

  takeRest,
  thenDebug,
  trim,
  toFullWidth,
  toHalfWidth,

  throttle,
  md5,

  absPath,

  recursiveIter,
  lastItem,
  recursiveIterCallstack,

  deDuplicateRun,

  getTreeLeafCnt,
  number2String,

  getFiles,
  handleWindwowFileNameSpecialCharacters,

  compareStrings,

  toBase,
  parseIntString,

  isDesktop,
  shortUUID,

  string2BigInt,
  isSingleQuotedChar,
  parseSingleQuotedChar,
};

exports = Object.assign(exports, module.exports);
